﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using System.Xml;
using MacomberMapSystem.Common.Database;
using MapTools;
using System.Runtime.InteropServices;
using MacomberMapSystem.Common.CIM;
using MacomberMapSystem.Common.Integration;
using System.IO;

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// (C) 2013, Michael E. Legatt, Ph.D., All Rights Reserved.
    /// This class exports a GIS-compliant shape file of substations and lines
    /// </summary>
    public class CIM_Shapefile_Exporter : CIM_Processor
    {
        /// <summary>
        /// Initialize our shapefile exporter
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="NamedParameters"></param>
        /// <param name="Repository"></param>
        public CIM_Shapefile_Exporter(XmlElement xElem, Dictionary<String, Object> NamedParameters, MM_Repository Repository)
            : base(xElem, NamedParameters, Repository)
        { }

        /// <summary>
        /// Report the name of our process
        /// </summary>
        public override string ProcessName
        {
            get { return "Shapefile Exporter"; }
        }

        /// <summary>
        /// Prepare our outputs
        /// </summary>
        /// <returns></returns>
        private Dictionary<string, List<CIM_Element>> PrepareOutputs()
        {
            //First, build our list of elements to be processed
            MM_Database_Model Model = NamedParameters["Model"] as MM_Database_Model;

            Dictionary<string, List<CIM_Element>> Outputs = new Dictionary<string, List<CIM_Element>>(StringComparer.CurrentCultureIgnoreCase);
            Outputs.Add("Wind Units", new List<CIM_Element>());
            Outputs.Add("Solar Units", new List<CIM_Element>());
            Outputs.Add("Gas Units", new List<CIM_Element>());
            Outputs.Add("Coal Units", new List<CIM_Element>());
            Outputs.Add("Hydro Units", new List<CIM_Element>());
            Outputs.Add("Nuclear Units", new List<CIM_Element>());
            Outputs.Add("Other Units", new List<CIM_Element>());
            Outputs.Add("All Units", new List<CIM_Element>());
            Outputs.Add("Unit Substations", new List<CIM_Element>());
            Outputs.Add("Capacitor Substations", new List<CIM_Element>());
            Outputs.Add("Reactor Substations", new List<CIM_Element>());
            Outputs.Add("PricingVector Substations", new List<CIM_Element>());
            Outputs.Add("StaticVarCompensator Substations", new List<CIM_Element>());
            Outputs.Add("Load Substations", new List<CIM_Element>());
            Outputs.Add("LAAR Substations", new List<CIM_Element>());
            Outputs.Add("All Substations", new List<CIM_Element>());
            Outputs.Add("345 KV Substations", new List<CIM_Element>());
            Outputs.Add("138 KV Substations", new List<CIM_Element>());
            Outputs.Add("69 KV Substations", new List<CIM_Element>());
            Outputs.Add("345 KV Lines", new List<CIM_Element>());
            Outputs.Add("138 KV Lines", new List<CIM_Element>());
            Outputs.Add("69 KV Lines", new List<CIM_Element>());
            Outputs.Add("All Lines", new List<CIM_Element>());

            //Go through our list of equipment and decide where to place them
            Total = Model.Substations.Count + Model.Lines.Count;
            Current = 0;
            List<CIM_Element> FoundList;
            foreach (CIM_Element Substation in Model.Substations.Values)
            {
                Current++;
                if (XmlConvert.ToBoolean(Substation["etx:Substation.forNetwork"]) && XmlConvert.ToBoolean(Substation["etx:Substation.forScada"]))
                {
                    //Add in our voltage levels
                    foreach (String VoltageLevel in Substation.VoltageLevels.Split(','))
                        if (Outputs.TryGetValue(VoltageLevel + " KV Substations", out FoundList))
                            FoundList.Add(Substation);

                    foreach (CIM_Element Unit in Substation.Parse("etx:ResourceController<Unit", null))
                    {
                        if (Outputs.TryGetValue(Unit.PrimaryFuelType + " Units", out FoundList))
                            FoundList.Add(Unit);
                        else
                            Console.WriteLine("Ignoring primary fuel type " + Unit.PrimaryFuelType + " for " + Unit.Substation.Name + "." + Unit.Name);
                        if (Outputs.TryGetValue("All Units", out FoundList))
                            FoundList.Add(Unit);
                    }

                    if (Outputs.TryGetValue("All Substations", out FoundList))
                        FoundList.Add(Substation);


                    List<String> ElemTypes = new List<string>();
                    foreach (CIM_Element Elem in Substation.Elements)
                    {
                        String ElemType = Elem.ElemType.Replace("Resistor", "");
                        if (!ElemTypes.Contains(ElemType))
                            ElemTypes.Add(ElemType);
                        ElemTypes.Sort();
                    }
                    foreach (String ElemType in ElemTypes)
                        if (Outputs.TryGetValue(ElemType + " Substations", out FoundList))
                            FoundList.Add(Substation);
                }
            }

            //Go through our list of lines
            foreach (CIM_Element Line in Model.Lines.Values)
            {
                if (Outputs.TryGetValue("All Lines", out FoundList))
                    FoundList.Add(Line);
                if (Outputs.TryGetValue(Line.VoltageLevel.Name + " Lines", out FoundList))
                    FoundList.Add(Line);
                Current++;
            }
            return Outputs;
        }


        /// <summary>
        /// Initiate our process
        /// </summary>
        /// <param name="DbConn"></param>
        /// <returns></returns>
        public override CIM_Processor.enumProcessState InitiateProcess(Database.MM_Database_Connector DbConn)
        {
            Dictionary<String, List<CIM_Element>> Outputs = PrepareOutputs();


            //Make sure our directory exists

            if (!Directory.Exists(Path.Combine(NamedParameters["TargetFolder"].ToString(), "ShapeFiles")))
                Directory.CreateDirectory(Path.Combine(NamedParameters["TargetFolder"].ToString(), "ShapeFiles"));


            //Write out our audit file
            using (System.IO.StreamWriter sW = new System.IO.StreamWriter(System.IO.Path.Combine(NamedParameters["TargetFolder"].ToString(), "ShapeFiles", "ShapeFileSummary.txt")))
                foreach (KeyValuePair<string, List<CIM_Element>> kvp in Outputs)
                {
                    sW.WriteLine("{0}:{1}", kvp.Key, kvp.Value.Count.ToString());
                    ProcessLog.AppendLine(kvp.Key + ": " +  kvp.Value.Count.ToString("#,##0"));
                }
            //Write out our output files
            enumProcessState FinalState = enumProcessState.Completed;
            foreach (KeyValuePair<string, List<CIM_Element>> kvp in Outputs)
                    if (kvp.Value.Count > 0)
                        try
                        {
                            WriteShapeFile(kvp.Key, kvp.Value, Repository);
                        }
                        catch (Exception ex)
                        {
                            FinalState = enumProcessState.CompletedWithErrors;
                            ProcessLog.AppendLine("Error writing " + kvp.Key + ": " + ex.ToString());
                        }

            return FinalState;
        }

        /// <summary>
        /// Write out the header and corresponding variables
        /// </summary>
        /// <param name="Title"></param>
        /// <param name="TargetFolder"></param>
        /// <param name="Shp"></param>
        /// <param name="Dbf"></param>
        private static void WriteHeader(String Title, String TargetFolder, out IntPtr Shp, out IntPtr Dbf)
        {
            if (Title.EndsWith("Lines"))
                Shp = ShapeLib.SHPCreate(Path.Combine(TargetFolder, "ShapeFiles", Title + ".shp"), ShapeLib.ShapeType.PolyLine);
            else
                Shp = ShapeLib.SHPCreate(Path.Combine(TargetFolder, "ShapeFiles", Title + ".shp"), ShapeLib.ShapeType.Point);
            Dbf = ShapeLib.DBFCreate(Path.Combine(TargetFolder, "ShapeFiles", Title + ".dbf"));

            //Now, create our database structure
            ShapeLib.DBFAddField(Dbf, "name", ShapeLib.DBFFieldType.FTString, 20, 0);
            ShapeLib.DBFAddField(Dbf, "teid", ShapeLib.DBFFieldType.FTInteger, 16, 0);
            ShapeLib.DBFAddField(Dbf, "owners", ShapeLib.DBFFieldType.FTString, 30, 0);
            ShapeLib.DBFAddField(Dbf, "operators", ShapeLib.DBFFieldType.FTString, 30, 0);

            if (Title.EndsWith("Units"))
            {
                ShapeLib.DBFAddField(Dbf, "primaryFuel", ShapeLib.DBFFieldType.FTString, 10, 0);
                ShapeLib.DBFAddField(Dbf, "secondaryFuel", ShapeLib.DBFFieldType.FTString, 10, 0);
                ShapeLib.DBFAddField(Dbf, "opcap", ShapeLib.DBFFieldType.FTDouble, 32, 4);
            }
            else if (Title.EndsWith("Substations"))
            {
                ShapeLib.DBFAddField(Dbf, "county", ShapeLib.DBFFieldType.FTString, 20, 0);
                ShapeLib.DBFAddField(Dbf, "loadzone", ShapeLib.DBFFieldType.FTString, 20, 0);
                ShapeLib.DBFAddField(Dbf, "weatherzone", ShapeLib.DBFFieldType.FTString, 20, 0);
                ShapeLib.DBFAddField(Dbf, "PSSEids", ShapeLib.DBFFieldType.FTString, 40, 0);
                ShapeLib.DBFAddField(Dbf, "MaxKv", ShapeLib.DBFFieldType.FTDouble, 32, 0);
            }
            else if (Title.EndsWith("Lines"))
            {
                ShapeLib.DBFAddField(Dbf, "fromSub", ShapeLib.DBFFieldType.FTString, 50, 0);
                ShapeLib.DBFAddField(Dbf, "toSub", ShapeLib.DBFFieldType.FTString, 50, 0);
                ShapeLib.DBFAddField(Dbf, "estLen", ShapeLib.DBFFieldType.FTDouble, 32, 4);
                ShapeLib.DBFAddField(Dbf, "modelLen", ShapeLib.DBFFieldType.FTDouble, 32, 4);
                ShapeLib.DBFAddField(Dbf, "voltage", ShapeLib.DBFFieldType.FTDouble, 32, 4);
            }
        }

        /// <summary>
        /// Write out our shape file
        /// </summary>
        /// <param name="Title"></param>
        /// <param name="Elements"></param>
        /// <param name="Repository"></param>        
        private void WriteShapeFile(String Title, List<CIM_Element> Elements, MM_Repository Repository)
        {
            //First, create our output file
            IntPtr Shp, Dbf;
            MM_Substation FoundSub;
            MM_Line FoundLine;
            WriteHeader(Title, NamedParameters["TargetFolder"].ToString(), out Shp, out Dbf);


            //Now, write out all our elements
            int CurShape = 0;
            foreach (CIM_Element Elem in Elements)
            {
                Console.WriteLine("Writing " + Elem.ToString());
                List<Double> XCoord = new List<double>(), YCoord = new List<double>();

                if (Elem.TypeName == "cim:ACLineSegment")
                {
                    if (Repository.Db.Lines.TryGetValue(Elem.rdfID, out FoundLine))
                        foreach (System.Drawing.PointD pt in FoundLine.Coordinates)
                        {
                            XCoord.Add(pt.X);
                            YCoord.Add(pt.Y);
                        }
                    else
                        foreach (CIM_Element Sub in Elem.ACSegmentStations)
                            if (Repository.Db.Substations.TryGetValue(Sub.rdfID, out FoundSub))
                            {
                                XCoord.Add(FoundSub.Longitude);
                                YCoord.Add(FoundSub.Latitude);
                            }
                }
                else if (Elem.TypeName == "cim:Substation")
                {
                    if (Repository.Db.Substations.TryGetValue(Elem.rdfID, out FoundSub))
                    {
                        XCoord.Add(FoundSub.Longitude);
                        YCoord.Add(FoundSub.Latitude);
                    }
                }
                else if (Elem.TypeName.EndsWith("Unit"))
                {
                    if (Repository.Db.Substations.TryGetValue(Elem.Substation.rdfID, out FoundSub))
                    {
                        XCoord.Add(FoundSub.Longitude);
                        YCoord.Add(FoundSub.Latitude);
                    }
                }

                //Now, add in our element
                IntPtr NewElem = ShapeLib.SHPCreateObject(XCoord.Count == 1 ? ShapeLib.ShapeType.Point : ShapeLib.ShapeType.PolyLine, -1, 0, null, null, XCoord.Count, XCoord.ToArray(), YCoord.ToArray(), new double[2], null);
                int Resp = ShapeLib.SHPWriteObject(Shp, -1, NewElem);
                ShapeLib.SHPDestroyObject(NewElem);

                if (Title.EndsWith("Units"))
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 0, Elem.Substation.Name + "." + Elem.Name));
                else if (Title.EndsWith("Lines"))
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 0, Elem.LineName));
                else
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 0, Elem.Name));
                TestResult(ShapeLib.DBFWriteIntegerAttribute(Dbf, CurShape, 1, Convert.ToInt32(Elem.TEID)));

                StringBuilder sOwner = new StringBuilder();
                foreach (KeyValuePair<CIM_Element, float> Company in Elem.ResourceOwners)
                    if (Company.Value == 100f)
                        sOwner.Append(Company.Key["cim:IdentifiedObject.aliasName"]);
                    else
                        sOwner.AppendLine(Company.Key["cim:IdentifiedObject.aliasName"] + (Company.Value / 100f).ToString(" (0%)"));
                TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 2, sOwner.ToString()));

                StringBuilder sOperator = new StringBuilder();
                foreach (KeyValuePair<CIM_Element, float> Company in Elem.ResourceOperators)
                    if (Company.Value == 100f)
                        sOperator.Append(Company.Key["cim:IdentifiedObject.aliasName"]);
                    else
                        sOperator.AppendLine(Company.Key["cim:IdentifiedObject.aliasName"] + (Company.Value / 100f).ToString(" (0%)"));
                TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 3, sOperator.ToString()));

                if (Title.EndsWith("Units"))
                {
                    String FuelType = Elem["etx:GeneratingUnit.PrimaryFuelType"];
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 4, FuelType.Substring(FuelType.LastIndexOf('.') + 1)));
                    FuelType = Elem["etx:GeneratingUnit.SecondaryFuelType"];
                    if (String.IsNullOrEmpty(FuelType))
                    {
                        TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 5, "?"));
                        Console.WriteLine("Warning: empty secondary fuel type for " + Elem.ToString());
                    }
                    else
                        TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 5, FuelType.Substring(FuelType.LastIndexOf('.') + 1)));

                    TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 6, Double.Parse(Elem["etx:GeneratingUnit.highReasonabilityLimit"])));
                }
                else if (Title.EndsWith("Substations") && Repository.Db.Substations.TryGetValue(Elem.rdfID, out FoundSub))
                {
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 4, FoundSub.Boundary.Name));
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 5, Elem.ParseSingle("etx:SettlementLoadZone", null).Name)); //LoadZone
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 6, Elem.ParseSingle("cim:SubGeographicalRegion", null).Name)); //WeatherZone
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 7, Elem.PSSEBusNumbers)); //WeatherZone
                    double MaxVoltage = 0, CurVoltage;
                    foreach (string Str in Elem.VoltageLevels.Split(','))
                        if (Double.TryParse(Str, out CurVoltage))
                            MaxVoltage = Math.Max(MaxVoltage, CurVoltage);
                    TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 8, MaxVoltage));
                }
                else if (Title.EndsWith("Lines"))
                {
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 4, Elem.ACSegmentStations[0].Name));
                    TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 5, Elem.ACSegmentStations[1].Name));
                    Double LineDistance;

                    if (Repository.Db.Lines.TryGetValue(Elem.rdfID, out FoundLine))
                        LineDistance = CIM_Model_Maintenance.EstimateDistance(FoundLine);
                    else
                        LineDistance = CIM_Model_Maintenance.EstimateDistance(Repository.Db.Substations[Elem.ACSegmentStations[0].rdfID], Repository.Db.Substations[Elem.ACSegmentStations[1].rdfID]);
                    TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 6, LineDistance));
                    TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 7, Convert.ToDouble(Elem["cim:Conductor.length"])));
                    TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 8, Convert.ToDouble(Elem.VoltageLevel["cim:BaseVoltage.nominalVoltage"])));
                }

                CurShape++;
            }
            ShapeLib.SHPClose(Shp);
            ShapeLib.DBFClose(Dbf);
        }

        public static void Test()
        {
            IntPtr Shp, Dbf;
            Random r = new Random();
            String Title = "TestLines";
            int CurShape = 0;
            WriteHeader(Title, @"C:\users\mlegatt\desktop", out Shp, out Dbf);
            for (int a = 0; a < 5; a++)
            {
                double[] XCoord = new double[] { r.NextDouble() + 30, r.NextDouble() + 30 };
                double[] YCoord = new double[] { -97 - (r.NextDouble()), -97 - (2*r.NextDouble()) };
                double[] ZCoord = new double[] { 0,0};
                //Now, add in our element
                IntPtr NewElem = ShapeLib.SHPCreateObject(XCoord.Length == 1 ? ShapeLib.ShapeType.Point : ShapeLib.ShapeType.PolyLine,-1,0, null, null, XCoord.Length, XCoord, YCoord, ZCoord, null);
                int Resp=ShapeLib.SHPWriteObject(Shp, -1, NewElem);
                ShapeLib.SHPDestroyObject(NewElem);

                 TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 0, "Line" + CurShape.ToString()));                
               TestResult( ShapeLib.DBFWriteIntegerAttribute(Dbf, CurShape, 1, Convert.ToInt32(r.Next(0,255))));

                StringBuilder sOwner = new StringBuilder("TMIKE");
                TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 2, sOwner.ToString()));

                StringBuilder sOperator = new StringBuilder("QMIKE");
                TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 3, sOperator.ToString()));
                TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 4, "FromSub"));
                TestResult(ShapeLib.DBFWriteStringAttribute(Dbf, CurShape, 5, "ToSub"));
                Double LineDistance = r.NextDouble() * 20;
                TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 6, LineDistance));
                TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 7, Convert.ToDouble(r.NextDouble()*20)));
                TestResult(ShapeLib.DBFWriteDoubleAttribute(Dbf, CurShape, 8, new double[] { 345,138,69}[r.Next(0,3)]));                             
            }
        CurShape++;

                ShapeLib.SHPClose(Shp);
                ShapeLib.DBFClose(Dbf);
            }

        private static void TestResult(int inResp)
        {
            if (inResp != 1)
            { }
        }
    }
}